﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Tools.TraceVisualiser.SqlParser
{
	#region ABSTRACT CLASS SqlParseCommand : PŘEDEK OBECNÉ TŘÍDY, KTERÁ DO SEBE VSTŘEBÁ SQL PŘÍKAZ A UMOŽNÍ JEJ LOGICKY ZPRACOVÁVAT
	/// <summary>
	/// class SqlParseCommand : předek tříd, které deklarují konkrétní SQL příkazy.
	/// </summary>
	public abstract class SqlParseCommand
	{
		#region KONSTRUKCE, PROPERTY
		public SqlParseCommand()
		{
			this.SyntaxDeclaration = new SqlParseSyntaxDeclaration();
		}
		/// <summary>
		/// Vztah na parent SQL text
		/// </summary>
		public SqlText ParentText { get; protected set; }
		/// <summary>
		/// Metoda, která do SQL příkazu vloží referenci na parent SQL text.
		/// Slouží k realizaci pozdější možnosti modifikovat příkaz včetně parent textu.
		/// Nedávám to do konstruktoru, protože instance třídy (tedy instance potomků, protože tato třída je abstract) 
		/// jsou vytvářeny aktivátorem, a tam parametr nedáváme. Komplikoval bych tvorbu potomstva.
		/// </summary>
		/// <param name="parentText"></param>
		public void SetParentText(SqlText parentText)
		{
			this.ParentText = parentText;
		}
		/// <summary>
		/// Deklarace syntaxe příkazu
		/// </summary>
		protected SqlParseSyntaxDeclaration SyntaxDeclaration { get; set; }
		/// <summary>
		/// Seznam syntaktických položek tohoto příkazu po jeho parsování, použité položky
		/// </summary>
		public IEnumerable<SqlParseSyntaxItem> SyntaxItemsUsed { get { return this.SyntaxDeclaration.SyntaxItemsUsed; } }
		/// <summary>
		/// Seznam syntaktických položek tohoto příkazu po jeho parsování, 
		/// </summary>
		public IEnumerable<SqlParseSyntaxItem> SyntaxItemsAll { get { return this.SyntaxDeclaration.SyntaxItemsAll; } }
		#endregion
		#region DEKODÉR SYNTAXE - obecný
		/// <summary>
		/// Vstupní metoda, na vstupu je fronta SQL segmentů, tento objekt si z ní odebírá položky tak dlouho, dokud jsou pro něj vyhovující.
		/// Zpracovává je do své struktury, a odebírá si další.
		/// První segment je segment, který odpovídá jednomu ze zdejších klíčových slov (KeyWordList). To zajišťuje vyšší level.
		/// Před odebráním se nejdřív podívá (Peek), a pokud další segment nezpracuje, vrací řízení.
		/// </summary>
		/// <param name="sqlQueue">Vstupní fronta SQL segmentů</param>
		public virtual void LoadFromQueue(Queue<SqlParseItem> sqlQueue)
		{
			this.SyntaxDeclare(sqlQueue);
			this.LoadCommandFromQueue(sqlQueue);
		}
		/// <summary>
		/// Dekodér syntaxe obecný.
		/// Vstupní metoda, na vstupu je fronta SQL segmentů, tento objekt si z ní odebírá položky tak dlouho, dokud jsou pro něj vyhovující.
		/// Zpracovává je do své struktury, a odebírá si další.
		/// První segment je segment, který odpovídá jednomu ze zdejších klíčových slov (KeyWordList). To zajišťuje vyšší level.
		/// Před odebráním se nejdřív podívá (Peek), a pokud další segment nezpracuje, vrací řízení.
		/// </summary>
		/// <param name="sqlQueue">Vstupní fronta SQL segmentů</param>
		protected virtual void LoadCommandFromQueue(Queue<SqlParseItem> sqlQueue)
		{
			this.SyntaxDeclaration.LoadCommandFromQueue(sqlQueue);

			// Zkontroluj svůj příkaz po jeho dokončení:
			this.SyntaxDeclaration.CheckCompleted();

			// Řekni potomkovi, že je načteno. Může si něco dodělat:
			this.LoadCommandAfter();
		}
		/// <summary>
		/// Háček pro potomka, když je SQL command načten.
		/// </summary>
		protected virtual void LoadCommandAfter()
		{ }
		#endregion
		#region ABSTRAKTNÍ METODY a PROPERTY, KTERÉ MUSÍ PŘEPSAT POTOMEK
		/// <summary>
		/// Klíčové slovo tohoto příkazu (SELECT, UPDATE, INSERT, DELETE, CREATE, ALTER, ...)
		/// </summary>
		public abstract string SqlCommand { get; }
		/// <summary>
		/// Souhrn všech klíčových slov, které tento objekt dokáže obsluhovat.
		/// Tento List se čte typicky jen jednou, při iniciaci systému.
		/// Klíčová slova mají být Trim().ToUpper().
		/// </summary>
		public abstract IEnumerable<string> KeyWordList { get; }
		/// <summary>
		/// Zde je třeba nadeklarovat položky syntaxe
		/// </summary>
		/// <param name="sqlQueue"></param>
		protected abstract void SyntaxDeclare(Queue<SqlParseItem> sqlQueue);
		/// <summary>
		/// Zajistí obalení aktuálního obsahu dané syntaktické fráze tak, aby na její konec mohl být přidán další výraz.
		/// SQL příkaz je this, dotyčná syntaktická fráze je v parametru syntaxItem, 
		/// přidávaný výraz je již parsován v poli v parametru sqlItems.
		/// Metoda není volána, pokud dotyčná syntaktická fráze (parametr syntaxItem) je dosud prázdný ani když je prázdný přidávaný výraz.
		/// Wrapování může pro snadné oddělení využít metod instanci SqlParseSyntaxItem: WrapWithComma(), WrapWithSpace(), WrapWithParenthesize()
		/// Anebo lze využít metody nižší úrovně: ParenthesiseContent(), AddContentOnBegin(), AddContentOnEnd()
		/// Pokud metoda chce obalit stávající content závorkami, je vhodné vyžít metodu SqlParseSyntaxItem.ParenthesiseContent().
		/// </summary>
		/// <param name="syntaxItem">Syntaktická položka</param>
		/// <param name="sqlItems">Seznam textových položek, které se budou přidávat (je možno nakouknout a přizpůsobit se).</param>
		public abstract void WrapSyntaxContent(SqlParseSyntaxItem syntaxItem, List<SqlParseItem> sqlItems);
		#endregion
		#region PODPORA MODIFIKACÍ SYNTAXE - PŘESUN ČÁSTI CONTENT DO KEYWORD
		/// <summary>
		/// Najde syntaxi daného jména (keyWord), a přesune z jejího začátku slova odpovídající daným modifikacím do KeyWordListu.
		/// Typicky se použije například na frázi "ORDER" pro přesun pomocného slova "BY" z Contentu do keywordu.
		/// Jako první parametr se předává klíčové slovo syntaxe.
		/// Další parametry jsou soupis doplňkových slov, která mohou být na začátku Contentu.
		/// Slov může být libovolné množství.
		/// Každý jeden string vyjadřuje jednu kombinaci textu (i víceslovnou, pokud má být v explicitním formátu).
		/// Příklady: "BY", "WITH TIES", "DISTINCT", "TOP * PERCENT" (kde * zastupuje libovolný výraz).
		/// </summary>
		/// <param name="keyWord">Klíčové slovo syntaxe</param>
		/// <param name="modList">Seznam doplňkových slov k přesunutí z Content do Keyword.</param>
		protected void SyntaxModify(string keyWord, params string[] modList)
		{
			SqlParseSyntaxItem syntax = this.SyntaxFind(keyWord);
			if (syntax != null)
				syntax.SyntaxModify(modList);
		}
		#endregion
		#region PODPORA NAPLNĚNÍ DATY TOHOTO SYNTAKTICKÉHO DEKODÉRU (typicky metody pro potomky)
		/// <summary>
		/// Vynuluje registry syntaxe
		/// </summary>
		protected void SyntaxClear()
		{
			this.SyntaxDeclaration.Clear();
		}
		/// <summary>
		/// Zadá nový prvek syntaxe
		/// </summary>
		/// <param name="keyWord">Klíčové slovo hlavní, pod nímž je prvek reprezentován (například "JOIN", i když lze očekávat i slova "LEFT OUTER JOIN")</param>
		/// <param name="isCompulsory">Je prvek povinný?</param>
		protected void SyntaxAdd(string keyWord, bool isCompulsory)
		{
			this.SyntaxAdd(keyWord, isCompulsory, null);
		}
		/// <summary>
		/// Zadá nový prvek syntaxe
		/// </summary>
		/// <param name="keyWord">Klíčové slovo hlavní, pod nímž je prvek reprezentován (například "JOIN", i když lze očekávat i slova "LEFT OUTER JOIN")</param>
		/// <param name="isCompulsory">Je prvek povinný?</param>
		/// <param name="keyWordList">Seznam variant příkazu, v normalizované podobě:
		/// UPPER, mezi slovy přesně jedna mezera,
		/// jednotlivé varianty musí být odděleny čárkou nebo středníkem nebo svislítkem (plus volitelně mezery).
		/// Příklad: "LEFT OUTER JOIN, LEFT JOIN, JOIN"</param>
		protected void SyntaxAdd(string keyWord, bool isCompulsory, string keyWordList)
		{
			this.SyntaxDeclaration.SyntaxAdd(new SqlParseSyntaxItem(this, keyWord, isCompulsory, keyWordList));
		}
		#endregion
		#region MODIFIKACE SQL PŘÍKAZU (ADD / MODIFY / REMOVE SYNTAX)
		/// <summary>
		/// Přidá do určité syntaktické fráze nový výraz.
		/// Vrátí true, když se to podařilo.
		/// </summary>
		/// <param name="syntax">Klíčové slovo syntaktické fráze (SELECT, FROM, WHERE, ORDER : vždy jednoslovně)</param>
		/// <param name="expression">Přidávaný výraz</param>
		/// <param name="delimited">Oddělit od stávajícího obsahu (typicky čárka, závorky a AND, atd.)</param>
		/// <returns></returns>
		public bool AddSyntaxExpression(string syntax, string expression, bool delimited)
		{
			SqlParseSyntaxItem syntItem = SyntaxFind(syntax);
			if (syntItem == null) return false;

			return syntItem.AddSyntaxExpression(expression, delimited);
		}
		/// <summary>
		/// Přidá do určité syntaktické fráze nová slova do Keywords.
		/// Pouze taková, která tam ještě nejsou.
		/// </summary>
		/// <param name="syntax">Klíčové slovo syntaktické fráze (SELECT, FROM, WHERE, ORDER : vždy jednoslovně)</param>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <param name="addAfterWords">Seznam klíčových slov, za které by se měl dodaný text přidávat (insert after)</param>
		/// <returns></returns>
		public bool AddKeyWord(string syntax, string keywords, params string[] addAfterWords)
		{
			return this._AddKeyWord(syntax, keywords, addAfterWords);
		}
		/// <summary>
		/// Přidá do určité syntaktické fráze nová slova do Keywords.
		/// Pouze taková, která tam ještě nejsou.
		/// </summary>
		/// <param name="syntax">Klíčové slovo syntaktické fráze (SELECT, FROM, WHERE, ORDER : vždy jednoslovně)</param>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <param name="addAfterWords">Seznam klíčových slov, za které by se měl dodaný text přidávat (insert after)</param>
		/// <returns></returns>
		public bool AddKeyWord(string syntax, string keywords, IEnumerable<string> addAfterWords)
		{
			return this._AddKeyWord(syntax, keywords, addAfterWords);
		}
		/// <summary>
		/// Odebere dané položky z parent textu (protože SqlCommand je interně neuchovává).
		/// Neodebírá je z příslušné syntaxe. Typicky se odebírání položek volá zespodu, ze syntaxe.
		/// </summary>
		/// <param name="sqlItems"></param>
		public void RemoveItems(IEnumerable<SqlParseItem> sqlItems)
		{
			this.ParentText.RemoveItems(sqlItems);
		}
		/// <summary>
		/// Odebere danou položku z parent textu (protože SqlCommand je interně neuchovává)
		/// Neodebírá je z příslušné syntaxe. Typicky se odebírání položek volá zespodu, ze syntaxe.
		/// </summary>
		/// <param name="sqlItem"></param>
		public void RemoveItem(SqlParseItem sqlItem)
		{
			this.ParentText.RemoveItem(sqlItem);
		}
		/// <summary>
		/// Přidá do určité syntaktické fráze nová slova do Keywords.
		/// Pouze taková, která tam ještě nejsou.
		/// Privátní výkonná metoda.
		/// </summary>
		/// <param name="syntax">Klíčové slovo syntaktické fráze (SELECT, FROM, WHERE, ORDER : vždy jednoslovně)</param>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <param name="addAfterWords">Seznam klíčových slov, za které by se měl dodaný text přidávat (insert after)</param>
		/// <returns></returns>
		private bool _AddKeyWord(string syntax, string keywords, IEnumerable<string> addAfterWords)
		{
			SqlParseSyntaxItem syntItem = SyntaxFind(syntax);
			if (syntItem == null) return false;

			return syntItem.AddKeyWord(keywords, addAfterWords);
		}
		/// <summary>
		/// Najde a vrátí syntax daného klíčového slova.
		/// Nezávisle na tom, zda je či není použitá.
		/// Pokud dané klíčové slovo v tomto SQL příkazu neexistuje, vrátí null.
		/// </summary>
		/// <param name="syntax"></param>
		/// <returns></returns>
		public SqlParseSyntaxItem SyntaxFind(string syntax)
		{
			return this.SyntaxItemsAll.FirstOrDefault(syntItem => syntItem.KeyWord == syntax);
		}
		#endregion
		#region PRÁCE S DETACHOVANÝM SEZNAMEM POLOŽEK (dosud nejsou součástí this syntaxe ani parent commandu)
		/// <summary>
		/// Obalí závorkami daný seznam.
		/// Vytvořené závorky nevkládá do parent textu ano do parent syntaxe. Jen je přidá k dodanému seznamu.
		/// Předem zjistí, zda je to zapotřebí:
		/// a) když je text prázdný, a neobsahuje nic než Blank a Comment (a vyřazené položky), tak závorky nepřidává
		/// b) když obsah textu už je korektně ozávorkovaný, tak další úroveň nepřidává
		/// </summary>
		/// <param name="sqlItems"></param>
		public static void ParenthesiseItems(List<SqlParseItem> sqlItems)
		{
			// Nic významného v dodaném seznamu není (tzn. obsahuje jen Blank a Comment), nebudu to obalovat:
			if (!SqlParseItem.IsListSignificant(sqlItems))
				return;

			// Pokud ve výrazu už je všechno uzavřeno do závorek, nic nepřidává:
			if (IsParenthesiseCompleted(sqlItems))
				return;

			// Text není prázdný, a není ozávorkovaný => ozávorkuji ho teď:
			SqlText parentText = sqlItems[0].ParentText;   // Vlastník položek (starých i nových). Položka na indexu [0] existuje, jinak by nebylo IsListSignificant() == true.

			// Vložit závorky vlevo a vpravo:
			sqlItems.InsertRange(0, ParseText("(", parentText, SqlParseCommandComponent.BracketLeft));
			sqlItems.AddRange(ParseText(")", parentText, SqlParseCommandComponent.BracketRight));

			// Přepočítat úrovně závorek:
			SetBracketLevel(sqlItems);
		}
		/// <summary>
		/// Zjistí, zda všechny signifikantní výrazy v seznamu jsou uzavřeny v jedné obalující závorce.
		/// Pro text "(cislo = 20 and (hodnota = 1 or hodnota = 2))" vrací true.
		/// Pro text "cislo = 20 and (hodnota = 1 or hodnota = 2)" vrací false.
		/// Pro text "(cislo = 20) and (hodnota = 1 or hodnota = 2)" vrací false.
		/// Pro text "cislo = 20 and (hodnota = 1 or hodnota = 2)" vrací false.
		/// Pro text "/* Filtr */ (cislo = 20 and (hodnota = 1 or hodnota = 2))" vrací true.
		/// </summary>
		/// <param name="sqlItems"></param>
		/// <returns></returns>
		public static bool IsParenthesiseCompleted(IEnumerable<SqlParseItem> sqlItems)
		{
			// Nespoléhám na úrovně závorek v položkách, ani je do nich nebudu počítat. Určím si je zde.
			int bracketLevel = 0;
			bool eob = false;                           // Našli jsme konec závorky 1 úrovně. Další otevírací závorka je už nekompletní.
			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (!sqlItem.IsSqlSignificant) continue;   // Nevýznamné položky přeskočím. Řeším jen SqlText, Delimiter, Bracket, String.
				switch (sqlItem.TextComponent)
				{
					case SqlParseTextComponent.BracketLeft:
						if (eob)                        // Pokud už byla závorka, a nyní otevírám další, už není kompletní a vrátím false:
							return false;               //  Například: "(a = 1) (b = 2)" - což je nejen chyba, ale navíc neozávorkovaná.
						bracketLevel++;
						break;
					case SqlParseTextComponent.BracketRight:
						if (bracketLevel <= 0)          // Snížení úrovně je jen do úrovně 0, ne do záporných čísel.
							return false;               // Záporná závorka vrací false : " (a = 20))"
						bracketLevel--;
						if (bracketLevel == 0)
							eob = true;                 // Pokud skončila poslední závorka, zapamatuji si, že byla a skončila. Další už nedovolím.
						break;
					default:
						// SqlText, String, Delimiter:
						if (bracketLevel == 0)          // Pokud se vyskytne nějaký text mimo závorky (v nulté úrovni, mezi závorkami),
							return false;               //   skončím s false:
						break;
				}
			}
			return true;
		}
		/// <summary>
		/// Do položek v daném listu vloží správnou úroveň závorek. Do všech položek v seznamu.
		/// </summary>
		/// <param name="sqlItems"></param>
		internal static void SetBracketLevel(IEnumerable<SqlParseItem> sqlItems)
		{
			int bracketLevel = 0;
			foreach (SqlParseItem sqlItem in sqlItems)
			{
				int currentBracketLevel = bracketLevel;
				switch (sqlItem.TextComponent)
				{
					case SqlParseTextComponent.BracketLeft:
						bracketLevel++;
						currentBracketLevel = bracketLevel;   // Otevírací závorka už ponese svoji úroveň BracketLevel, ne tu předchozí
						break;
					case SqlParseTextComponent.BracketRight:
						if (bracketLevel > 0)                 // Snížení úrovně je jen do úrovně 0, ne do záporných čísel.
							bracketLevel--;                   // Koncová závorka ještě ponese úroveň BracketLevel platnou na jejím začátku
						break;
				}
				sqlItem.SetBracketLevel(currentBracketLevel); // Do položky vložit úroveň závorek
			}
		}
		/// <summary>
		/// Rozdělí zadaný výraz na jednotlivé položky, vloží do nich daný parent text, a vloží do nich druh cmdComponent.
		/// Pokud je parsován Keyword, pak jako cmdComponent se předává SqlParseCommandComponent.KeyWord, pokud je parsován Content, pak cokoliv.
		/// </summary>
		/// <param name="expression">Text k parsování</param>
		/// <param name="parentText">Parent text, bude vložen do přidaných položek "Závorka" jako jejich parent.</param>
		/// <param name="cmdComponent">Druh textu: Pokud je parsován Keyword, pak jako cmdComponent se předává SqlParseCommandComponent.KeyWord, pokud je parsován Content, pak cokoliv.</param>
		/// <returns></returns>
		public static List<SqlParseItem> ParseText(string expression, SqlText parentText, SqlParseCommandComponent cmdComponent)
		{
			// Prázdný výraz? Končím:
			if (expression == null || expression.Length == 0) return new List<SqlParseItem>();

			// Výraz rozdělím do položek (jednotlivá slova, oddělovače, stringy, mezery):
			List<SqlParseItem> sqlItems = SqlTextParser.ParseExpression(expression, parentText);
			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (cmdComponent == SqlParseCommandComponent.KeyWord)
				{
					if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
						sqlItem.SetCmdComponent(SqlParseCommandComponent.KeyWord);
				}
				else
				{
					sqlItem.SetCmdComponentContent();
				}
			}
			return sqlItems;
		}
		#endregion
		#region ABSTRACT CLASS FACTORY : pro dané klíčové slovo vrátí instanci třídy, která je implementací tohoto SQL příkazu
		/// <summary>
		/// Soupis tříd, které jsou potomkem SqlParseCommand a mohou tedy pro různé SQL příkazy zajišťovat jejich logické parsování
		/// </summary>
		private static Dictionary<string, Type> _ClassDict;
		/// <summary>
		/// Pro dané klíčové slovo (první slovo SQL příkazu) vrátí nově vytvořený objekt, který jej obsluhuje.
		/// Object factory.
		/// </summary>
		/// <param name="parseItem"></param>
		/// <returns></returns>
		public static SqlParseCommand CreateSqlParseCommand(SqlParseItem parseItem)
		{
			if (parseItem == null) return null;
			return CreateSqlParseCommand(parseItem.TextTrim);
		}
		/// <summary>
		/// Pro dané klíčové slovo (první slovo SQL příkazu) vrátí nově vytvořený objekt, který jej obsluhuje.
		/// Object factory.
		/// </summary>
		/// <param name="keyWord"></param>
		/// <returns></returns>
		public static SqlParseCommand CreateSqlParseCommand(string keyWord)
		{
			if (keyWord == null || keyWord.Length == 0) return null;
			PrepareClassDict();

			Type type;
			if (_ClassDict.TryGetValue(keyWord, out type))
			{
				object inst = null;
				try
				{
					inst = System.Activator.CreateInstance(type);
				}
				catch
				{
					inst = null;
				}
				if (inst != null && inst is SqlParseCommand)
				{
					return inst as SqlParseCommand;
				}
			}
			return null;
		}
		/// <summary>
		/// Zajistí, že slovník tříd _ClassDict bude obsahovat přehled tříd.
		/// </summary>
		private static void PrepareClassDict()
		{
			if (_ClassDict != null) return;

			Dictionary<string, Type> result = new Dictionary<string, Type>();

			// Tady by se spíš použila nějaká reflexní metoda, která načte typy které jsou potomkem SqlParseCommand a nejsou abstraktní:
			List<Type> classes = new List<Type>();
			classes.Add(typeof(SqlParseCommandSelect));

			foreach (Type type in classes)
			{
				if (type.IsAbstract) continue;
				if (type.IsInterface) continue;

				object inst = null;
				try
				{
					inst = System.Activator.CreateInstance(type);
				}
				catch
				{
					inst = null;
				}
				if (inst != null && inst is SqlParseCommand)
				{
					SqlParseCommand ispc = inst as SqlParseCommand;
					if (ispc != null)
					{
						IEnumerable<string> keyWordList = ispc.KeyWordList;
						if (keyWordList != null)
						{
							foreach (string keyWord in keyWordList)
							{
								if (!result.ContainsKey(keyWord))
									result.Add(keyWord, type);
							}
						}
					}
				}
			}

			// Uložím hotový výsledek (statická proměnná, na začátku se testuje not null, musím do ní najednou vložit kompletní dictionary).
			_ClassDict = result;
		}
		#endregion
	}
	#endregion
	#region CLASS SqlParseSyntaxDeclaration : 
	/// <summary>
	/// Deklarace syntaxe SQL příkazu
	/// </summary>
    public class SqlParseSyntaxDeclaration
	{
		public SqlParseSyntaxDeclaration()
		{
			this.Clear();
		}
		/// <summary>
		/// Seznam syntaktických položek příkazu
		/// </summary>
		public List<SqlParseSyntaxItem> SyntaxItemList { get; private set; }
		/// <summary>
		/// Aktuální pozice v SQL příkazu při jeho logickém parsování. Výchozí hodnota = -1.
		/// Odpovídá indexu prvku v seznamu this.SyntaxItemList.
		/// Právě se načítají jeho položky (alespoň posledně nalezená položka byla z této položky, co bude příště nevíme).
		/// </summary>
		public int CurrentItemIndex { get; private set; }
		/// <summary>
		/// Buffer segmentů SQL příkazu, které jsou před klíčovým slovem, před jeho definitivní detekcí (v režimu Partial).
		/// (například dvě položky: "LEFT" "OUTER"), když ještě očekáváme "JOIN".
		/// </summary>
		public List<SqlParseItem> SqlItemKeywordBuffer { get; private set; }
		/// <summary>
		/// Vstupní položka, kde došlo k chybě
		/// </summary>
		public SqlParseItem ErrorItem { get; private set; }
		/// <summary>
		/// Text chyby syntaxe
		/// </summary>
		public string ErrorText { get; private set; }
		/// <summary>
		/// true, pokud je v příkazu zachycena chyba (viz ErrorIndex, ErrorText)
		/// </summary>
		public bool ErrorInSyntax { get { return (this.ErrorItem != null); } }
		/// <summary>
		/// Vynuluje registry syntaxe
		/// </summary>
		public void Clear()
		{
			this.SyntaxItemList = new List<SqlParseSyntaxItem>();
			this.CurrentItemIndex = -1;
			this.SqlItemKeywordBuffer = new List<SqlParseItem>();
			this.ErrorItem = null;
			this.ErrorText = null;
		}
		/// <summary>
		/// Vloží do své definice další syntaktickou položku
		/// </summary>
		/// <param name="sqlParseSyntaxItem"></param>
		internal void SyntaxAdd(SqlParseSyntaxItem sqlSyntaxItem)
		{
			this.SyntaxItemList.Add(sqlSyntaxItem);
		}
		/// <summary>
		/// Seznam syntaktických položek tohoto příkazu po jeho parsování, skutečně se vyskytující položky
		/// </summary>
		public IEnumerable<SqlParseSyntaxItem> SyntaxItemsUsed { get { return this._GetCloneSyntaxItems(false); } }
		/// <summary>
		/// Seznam syntaktických položek tohoto příkazu po jeho parsování, 
		/// </summary>
		public IEnumerable<SqlParseSyntaxItem> SyntaxItemsAll { get { return this._GetCloneSyntaxItems(true); } }
		/// <summary>
		/// Vrátí izolovaný seznam prvků syntaxe
		/// </summary>
		/// <returns></returns>
		private List<SqlParseSyntaxItem> _GetCloneSyntaxItems(bool allItems)
		{
			List<SqlParseSyntaxItem> result = this.SyntaxItemList.FindAll(syntItem => allItems || syntItem.IsUsed);
			return result;
		}
		/// <summary>
		/// Aktuální prvek syntaxe
		/// </summary>
		public SqlParseSyntaxItem CurrentSyntaxItem
		{
			get
			{
				if (this.CurrentItemIndex < 0 || this.CurrentItemIndex >= this.SyntaxItemList.Count)
					return null;
				return this.SyntaxItemList[this.CurrentItemIndex];
			}
			set
			{
				int index = -1;
				if (value != null)
				{	// Najdu index prvku v poli this.SyntaxItemList, na kterém je uložen daný objekt (shoda reference):
					index = this.SyntaxItemList.FindIndex(item => Object.ReferenceEquals(item, value));
				}
				this.CurrentItemIndex = index;
			}
		}
		/// <summary>
		/// Aktuální klíčové slovo (= KeyWord z aktuální položky syntaxe CurrentSyntaxItem).
		/// </summary>
		public string CurrentKeyWord
		{
			get
			{
				SqlParseSyntaxItem currentSyntaxItem = this.CurrentSyntaxItem;
				if (currentSyntaxItem == null)
					return String.Empty;
				return currentSyntaxItem.KeyWord;
			}
		}
		#region DEKODÉR SYNTAXE
		/// <summary>
		/// Dekodér syntaxe obecný.
		/// Vstupní metoda, na vstupu je fronta SQL segmentů, tento objekt si z ní odebírá položky tak dlouho, dokud jsou pro něj vyhovující.
		/// Zpracovává je do své struktury, a odebírá si další.
		/// První segment je segment, který odpovídá jednomu ze zdejších klíčových slov (KeyWordList). To zajišťuje vyšší level.
		/// Před odebráním se nejdřív podívá (Peek), a pokud další segment nezpracuje, vrací řízení.
		/// </summary>
		/// <param name="sqlQueue">Vstupní fronta SQL segmentů</param>
		internal virtual void LoadCommandFromQueue(Queue<SqlParseItem> sqlQueue)
		{
			int bracketLevel = 0;
			while (sqlQueue.Count > 0)
			{
				SqlParseItem sqlItem = sqlQueue.Peek();

				// Závorky => pokud je jakýkoli výraz uveden v závorkách, 
				//  pak jeho vnitřek nezkoumám a vkládám všechny položky příkazu do aktuální položky syntaxe:
				bool isBracket = false;
				if (bracketLevel > 0 || sqlItem.TextComponent == SqlParseTextComponent.BracketLeft || sqlItem.TextComponent == SqlParseTextComponent.BracketRight)
				{	// Pokud jsem uvnitř nějaké závorky, anebo aktuální text je závorka (levá, pravá - vždy jednotlivě!):
					isBracket = true;
					int currentBracketLevel = bracketLevel;
					switch (sqlItem.TextComponent)
					{
						case SqlParseTextComponent.BracketLeft:
							bracketLevel++;
							currentBracketLevel = bracketLevel;  // Otevírací závorka už ponese svoji úroveň BracketLevel, ne tu předchozí
							break;
						case SqlParseTextComponent.BracketRight:
							bracketLevel--;                      // Koncová závorka ještě ponese úroveň BracketLevel platnou na jejím začátku
							break;
					}
					sqlItem.SetBracketLevel(currentBracketLevel);    // Do slova vložit úroveň závorek
				}
				else
					sqlItem.SetBracketLevel(0);                      // Do slova vložit úroveň závorek == 0

				// Zpracování jednoho slova:
				if (isBracket)
					// Pokud jsem v závorce, pak obsah vkládám přímo do obsahu aktuálního prvku syntaxe, bez dalšího zkoumání:
					this.StoreItemIntoCurrentSyntaxContent(sqlItem);
				else
				{	// Pokud kontrolor syntaxe odmítne následující slovo, necháme jej ve frontě (četli jsme jej přes Peek()) a skončíme:
					if (!this.IsCorrectItem(sqlItem))
						break;
				}

				// Kontrolor syntaxe dané slovo přijal (anebo šlo o položku v závorce, tu přijmout musel),
				// odebereme tedy položku z fronty a jdeme na další:
				sqlQueue.Dequeue();
			}
		}
		#endregion
		#region ZPRACOVÁNÍ JEDNOHO VSTUPNÍHO PRVKU, KTERÝ JE V PŘÍMÉ ŘADĚ TOHOTO PŘÍKAZU
		/// <summary>
		/// Zjistí, zda dané SQL slovo je korektní.
		/// Pokud ne, vrátí false.
		/// Pokud ano, zpracuje jej a vrátí true.
		/// Dané slovo není v závorkách, a není komentář ani blank.
		/// Může to být string.
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		internal bool IsCorrectItem(SqlParseItem sqlItem)
		{
			// Pokud jsme už dříve našli chybu, nic dalšího nebereme:
			if (this.ErrorInSyntax) return false;

			// Pokud je na vstupu SqlText, zkusím najít jeho vliv na syntaxi.
			// Pokud je na vstupu cokoli jiného (delimiter, string, literál, závorky), pak nemá vliv na syntaxi.
			// Poznámka: vliv závorek na běh textu je řešen o level výše, sem už SqlTexty v závorkách nechodí. 
			//           Ty chodí do metody this.StoreItemIntoCurrentSyntaxContent()
			SqlParseSyntaxMatchType nextMatch = SqlParseSyntaxMatchType.NoMatch;
			SqlParseSyntaxItem nextItem = null;

			if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
			{	// Pouze SqlText:
				// Korektní slovo je tehdy, když jej najdu v některé z budoucích větví SQL příkazu a nepřeskočím při tom některé povinné slovo,
				// anebo když jej v žádné větvi nenajdu (pak nejde o klíčové slovo, ale třeba o výraz = tabulka, sloupec, vzorec, atd).
				List<SqlParseSyntaxItem> nextSyntaxList = this._GetNextSyntaxList();        // Vrátí možné budoucí syntaktické prvky

				// Projdeme přípustná následující klíčová slova, a pokud některé z nich vyhovuje aktuálnímu slovu, pak jej aktivujeme:
				foreach (SqlParseSyntaxItem next in nextSyntaxList)
				{
					SqlParseSyntaxMatchType match = next.GetSyntaxMatch(sqlItem);
					if (match == SqlParseSyntaxMatchType.Equal)
					{	// Našli jsme přesně: hotovo
						nextItem = next;
						nextMatch = match;
						break;
					}
					if (match == SqlParseSyntaxMatchType.Partial && nextItem == null)
					{	// Našli jsme částečně: zapamatujeme si a hledáme dál
						nextItem = next;
						nextMatch = match;
					}
				}

				// Pokud jsem SqlText nenašel, zkontroluji zda nejde o základní klíčové slovo (=uvozující slovo jiného SQL příkazu):
				if (nextMatch == SqlParseSyntaxMatchType.NoMatch)
				{
					bool isCommandKeyWord = IsCommandKeyWord(sqlItem);
					if (isCommandKeyWord)
					{	// Klíčové slovo, kterým začíná další SQL příkaz:
						this._FlushBufferedItemIntoCurrentWord();
						return false;
					}

					// Pokud jsme slovo nenašli, ověříme zda se nachází kdekoliv v našich klíčových slovech 
					//  (to by pak znamenalo, že jde o syntaktickou chybu):
					bool isAnyKeyWord = this.IsAnyKeyword(sqlItem);
					if (isAnyKeyWord)
					{	// Je to nějaké naše vnitřní klíčové slovo (ale neočekávané v tomto místě):
						this.SqlItemKeywordBuffer.Clear();          // Předchozí slova (například "LEFT OUTER") zahodíme.
						this.ErrorItem = sqlItem;
						this.ErrorText = "Neočekávaný výskyt klíčového slova " + sqlItem.KeyWord + " v příkazu.";
						return false;
					}
				}
			}
			else
			{	// Jiné položky než SqlText
				nextMatch = SqlParseSyntaxMatchType.NoSqlText;
			}

			switch (nextMatch)
			{
				case SqlParseSyntaxMatchType.NoSqlText:
				case SqlParseSyntaxMatchType.NoMatch:
					// Není to klíčové slovo => jde o běžná slova v příkazu, anebo vůbec nejde o SqlText:
					SqlParseSyntaxItem currentSyntaxItem = this.CurrentSyntaxItem;
					if (currentSyntaxItem != null)
					{	// Běžné slovo přidávám do jeho parent syntaktické položky:
						this._FlushBufferedItemIntoCurrentWord(sqlItem);
					}
					else
					{	// Běžné slovo, ale dosud není aktivní žádná položka? Nejspíš chyba řízení:
						this.ErrorItem = sqlItem;
						this.ErrorText = "Neočekávaný výskyt slova " + sqlItem.KeyWord + " v příkazu bez úvodního klíčového slova.";
						return false;
					}
					return true;

				case SqlParseSyntaxMatchType.Partial:
					// Našli jsme částečnou shodu: vstupní slovo si zapamatujeme do bufferu 
					//  (ještě není definitivní, co nakonec vybereme za syntaxi):
					this.SqlItemKeywordBuffer.Add(sqlItem);
					return true;

				case SqlParseSyntaxMatchType.Equal:
					// Nalezené slovo je KeyWord:
					// Pokud jsme našli některé prefixy klíčového slova ("LEFT OUTER" ke slovu "JOIN"), vložíme je do klíčového slova nyní:
					if (this.SqlItemKeywordBuffer.Count > 0)
					{
						foreach (SqlParseItem sqlPrev in this.SqlItemKeywordBuffer)
						{
							sqlPrev.SetCmdComponent(SqlParseCommandComponent.KeyWord);
							nextItem.AddKeywordItem(sqlPrev);
						}
						this.SqlItemKeywordBuffer.Clear();
					}
					sqlItem.SetCmdComponent(SqlParseCommandComponent.KeyWord);
					nextItem.AddKeywordItem(sqlItem);                // Vložíme aktuální keyword
					this.CurrentSyntaxItem = nextItem;
					return true;
			}
			return false;
		}
		/// <summary>
		/// Pokud je otevřen aktuální prvek syntaxe (CurrentSyntaxItem není null) 
		/// a pokud jsou v bufferu nějaká nepřiřazená slova (this.SqlItemKeywordList.Count > 0),
		/// pak tato slova vloží do obsahu aktuálního prvku syntaxe (this.CurrentSyntaxItem.SqlItemContentList).
		/// </summary>
		private void _FlushBufferedItemIntoCurrentWord()
		{
			this._FlushBufferedItemIntoCurrentWord(null);
		}
		/// <summary>
		/// Pokud je otevřen aktuální prvek syntaxe (CurrentSyntaxItem není null) 
		/// a pokud jsou v bufferu nějaká nepřiřazená slova (this.SqlItemKeywordList.Count > 0),
		/// pak tato slova vloží do obsahu aktuálního prvku syntaxe (this.CurrentSyntaxItem.SqlItemContentList).
		/// </summary>
		private void _FlushBufferedItemIntoCurrentWord(SqlParseItem sqlItem)
		{
			SqlParseSyntaxItem currentSyntaxItem = this.CurrentSyntaxItem;
			if (currentSyntaxItem != null)
			{
				if (this.SqlItemKeywordBuffer.Count > 0)
				{
					foreach (SqlParseItem sqlPrev in this.SqlItemKeywordBuffer)
					{
						sqlPrev.SetCmdComponentContent();
						currentSyntaxItem.AddContentItemOnEnd(sqlPrev);
					}
					this.SqlItemKeywordBuffer.Clear();
				}
				if (sqlItem != null)
				{
					sqlItem.SetCmdComponentContent();
					_StoreItemIntoCurrentContent(sqlItem);
				}
			}
		}
		/// <summary>
		/// Pokud je otevřen aktuální prvek syntaxe (CurrentSyntaxItem není null) 
		/// a pokud jsou v bufferu nějaká nepřiřazená slova (this.SqlItemKeywordList.Count > 0),
		/// pak tato slova vloží do obsahu aktuálního prvku syntaxe (this.CurrentSyntaxItem.SqlItemContentList).
		/// </summary>
		private void _StoreItemIntoCurrentKeyword(SqlParseItem sqlItem)
		{
			if (sqlItem == null) return;

			SqlParseSyntaxItem currentSyntaxItem = this.CurrentSyntaxItem;
			if (currentSyntaxItem == null) return;

			currentSyntaxItem.AddKeywordItem(sqlItem);
		}
		/// <summary>
		/// Pokud je otevřen aktuální prvek syntaxe (CurrentSyntaxItem není null) 
		/// a pokud jsou v bufferu nějaká nepřiřazená slova (this.SqlItemKeywordList.Count > 0),
		/// pak tato slova vloží do obsahu aktuálního prvku syntaxe (this.CurrentSyntaxItem.SqlItemContentList).
		/// </summary>
		private void _StoreItemIntoCurrentContent(SqlParseItem sqlItem)
		{
			if (sqlItem == null) return;

			SqlParseSyntaxItem currentSyntaxItem = this.CurrentSyntaxItem;
			if (currentSyntaxItem == null) return;

			currentSyntaxItem.AddContentItemOnEnd(sqlItem);
		}
		/// <summary>
		/// Ověří, zda vstupní SQL text je nějaké klíčové slovo
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		private bool IsAnyKeyword(SqlParseItem sqlItem)
		{
			if (sqlItem.TextComponent != SqlParseTextComponent.SqlText)
				return false;

			foreach (SqlParseSyntaxItem syntaxItem in this.SyntaxItemList)
			{
				if (syntaxItem.IsAnyKeyword(sqlItem))
					return true;
			}
			return false;
		}
		/// <summary>
		/// Vrátí možné budoucí syntaktické prvky.
		/// Vrací od příštího prvku, v aktuální větvi příkazu, posledním prvkem je první povinný prvek.
		/// </summary>
		/// <returns></returns>
		private List<SqlParseSyntaxItem> _GetNextSyntaxList()
		{
			List<SqlParseSyntaxItem> result = new List<SqlParseSyntaxItem>();
			for (int i = this.CurrentItemIndex + 1; i < this.SyntaxItemList.Count; i++)
			{
				SqlParseSyntaxItem next = this.SyntaxItemList[i];    // Příští prvek
				result.Add(next);                 // Příští tam dáme
				if (next.IsCompulsory)            // A pokud je příští prvek povinný, tak na něm skončíme.
					break;
			}
			return result;
		}
		#endregion
		/// <summary>
		/// Daný SQL výraz vloží do aktuální položky syntaxe bez dalšího zkoumání. Je to část textu v závorce.
		/// </summary>
		/// <param name="sqlItem"></param>
		internal void StoreItemIntoCurrentSyntaxContent(SqlParseItem sqlItem)
		{
			this._StoreItemIntoCurrentContent(sqlItem);
		}
		/// <summary>
		/// Zkontroluje, zda je příkaz kompletní a správný.
		/// </summary>
		internal void CheckCompleted()
		{
			// throw new NotImplementedException();
		}
		#region STATIC METODY - DETEKCE PRVNÍHO KEYWORDU SQL PŘÍKAZU
		/// <summary>
		/// Zjistí, zda dané slovo je/není prvním klíčovým slovem nějakého SQL příkazu
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		internal static bool IsCommandKeyWord(SqlParseItem sqlItem)
		{
			if (sqlItem.TextComponent != SqlParseTextComponent.SqlText) return false;         // Nic jiného než SqlText nemůže být příkaz
			_PrepareCommandKeyWord();
			return _CommandKeyWords.Contains(sqlItem.KeyWord);
		}

		private static void _PrepareCommandKeyWord()
		{
			if (_CommandKeyWords == null)
			{
				string commands =
					"ALTER, BACKUP, BEGIN, BREAK, CLOSE, COMMIT, CONTINUE, CREATE, DBCC, DEALLOCATE, DECLARE, DELETE, DROP, " +
					"END, EXEC, EXECUTE, EXIT, FETCH, GOTO, GRANT, IF, INSERT, OPEN, " +
					"RAISERROR, RESTORE, RETURN, REVERT, ROLLBACK, SELECT, SET, TRUNCATE, UNION, UPDATE, UPDATETEXT, USE, WRITETEXT";

				List<string> cmds = new List<string>(commands.Split(','));
				_CommandKeyWords = new List<string>();
				foreach (string cmd in cmds)
				{
					string cmdt = cmd.Trim();
					if (cmdt.Length > 0)
						_CommandKeyWords.Add(cmdt);
				}
			}
		}
		private static List<string> _CommandKeyWords = null;
		#endregion
	}
	#endregion
	#region CLASS SqlParseSyntaxItem : jedna položka syntaxe = fráze příkazu včetně dat
	/// <summary>
	/// Syntaktická položka SQL příkazu, klíčové slovo v příkazu (například: SELECT, FROM, WHERE, ORDER)
	/// </summary>
    public class SqlParseSyntaxItem
	{
		#region KONSTRUKCE
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="keyWord">Klíčové slovo hlavní, pod nímž je prvek reprezentován (například "JOIN", i když lze očekávat i slova "LEFT OUTER JOIN")</param>
		/// <param name="isCompulsory">Je prvek povinný?</param>
		/// <param name="keyWordList">Seznam variant příkazu, v normalizované podobě:
		/// UPPER, mezi slovy přesně jedna mezera,
		/// jednotlivé varianty musí být odděleny čárkou nebo středníkem nebo svislítkem (plus volitelně mezery).
		/// Příklad: "LEFT OUTER JOIN, LEFT JOIN, JOIN"</param>
		public SqlParseSyntaxItem(SqlParseCommand parentCommand, string keyWord, bool isCompulsory, string keyWordList)
		{
			this.ParentSqlCommand = parentCommand;
			this.KeyWord = keyWord;
			this.IsCompulsory = isCompulsory;

			// Zpracovat list variant příkazu:
			List<string> kwList = new List<string>();
			kwList.Add(keyWord);                      // Na začátek dám vždycky klíčové slovo
			if (!String.IsNullOrEmpty(keyWordList))
			{
				string[] kws = keyWordList.Split(new char[] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string kw in kws)
				{
					string kwt = kw.Trim();
					if (kwt.Length > 0)
						kwList.Add(kwt);
				}
			}
			this.KeyWordList = kwList.ToArray();

			// Výstupní hodnoty
			this.SqlItemKeywordList = new List<SqlParseItem>();
			this.SqlItemContentList = new List<SqlParseItem>();
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			// Prázdno:
			if (!this.IsUsed) return "(" + this.KeyWord + ")";

			// Obsahuje konkrétní příkaz:
			string key = SqlParseItem.JoinToSqlText(this.SqlItemKeywordList);
			string con = SqlParseItem.JoinToSqlText(this.SqlItemContentList);

			string result = (((key.Length * con.Length) > 0) ? key + "   " + con : key + con);
			return result;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Klíčové slovo obecné (například ORDER)
		/// </summary>
		public string KeyWord { get; private set; }
		/// <summary>
		/// Je tato část syntaxe povinná ? Pokud ano, a v příkazu se nevyskytne, bude nastaven příznak chybné syntaxe)
		/// </summary>
		public bool IsCompulsory { get; private set; }
		/// <summary>
		/// Přehled možných sekvencí slov, které tvoří toto klíčové slovo. 
		/// Například: "LEFT OUTER JOIN", "OUTER JOIN", "JOIN", "LEFT JOIN", "RIGHT JOIN", "ORDER BY", "GROUP BY", ...
		/// Sekvence jsou zadávány jako jeden string v normalizované formě: UPPER, uvnitř právě jedna mezera.
		/// </summary>
		public string[] KeyWordList { get; private set; }
		/// <summary>
		/// Vztah na parent SQL příkaz, ve kterém je tento prvek syntaxe hostován.
		/// </summary>
		public SqlParseCommand ParentSqlCommand { get; private set; }
		/// <summary>
		/// Vztah na parent text, v němž je tato syntaxe uváděna. Vrací ParentText ze svého ParentSqlCommand, nebo null.
		/// </summary>
		public SqlText ParentText { get { return (this.ParentSqlCommand == null ? null : this.ParentSqlCommand.ParentText); } }
		/// <summary>
		/// Souhrn segmentů SQL příkazu, které jsou součástí klíčového slova.
		/// První klíčové slovo vkládá obecný parser (například "ORDER").
		/// Další slova sem vkládá konkrétní SqlCommand až po finalizaci příkazu, 
		/// přesouvá sem některá další slova z SqlItemContentList.
		/// Typicky klíčové slovo "ORDER BY" je neprve uloženo jako KeyWord = "ORDER" a Content " BY tabulka.sloupec ASC,...".
		/// Po finalizaci je ale text " BY" přesunut: KeyWord = "ORDER BY" a Content " tabulka.sloupec ASC,...".
		/// </summary>
		public List<SqlParseItem> SqlItemKeywordList { get; private set; }
		/// <summary>
		/// Souhrn segmentů SQL příkazu, které tvoří obsah této fráze, vyjma klíčového slova
		/// (například takovéto položky: "p.cislo_subjektu", "=", "123456", "and", "p.stav", "=", "'A'")
		/// </summary>
		public List<SqlParseItem> SqlItemContentList { get; private set; }
		/// <summary>
		/// Příznak, že tato položka je v příkazu použita (obsahuje některé slovo v části klíčových slov nebo v contentu).
		/// </summary>
		public bool IsUsed
		{
			get { return (this.SqlItemKeywordList.Count > 0 || this.SqlItemContentList.Count > 0); }
		}
		/// <summary>
		/// Obsahuje true, pokud v aktuálním Contentu je něco významného (tj. ignoruji případné položky Blank, Comment, Removed).
		/// </summary>
		public bool IsContentSignificant
		{ get { return SqlParseItem.IsListSignificant(this.SqlItemContentList); } }
		/// <summary>
		/// Obsahuje aktuální klíčové slovo ("SELECT", "UPDATE", "LEFT OUTER JOIN", "WHERE" ...)
		/// </summary>
		public string SqlKeyWordCurrent
		{
			get
			{
				string result = SqlParseItem.JoinToSqlText(this.SqlItemKeywordList);
				return result.ToUpper();
			}
		}
		/// <summary>
		/// Obsahuje aktuální obsah ("lcs.tabulka.sloupec as alias, sloupec2", nebo "cislo_subjektu = 24", ...)
		/// </summary>
		public string SqlContentCurrent
		{
			get
			{
				string result = SqlParseItem.JoinToSqlText(this.SqlItemContentList);
				return result;
			}
		}
		/// <summary>
		/// Obsahuje docela první slovo této syntaxe, ať už je to první položka KeyWord nebo Content
		/// </summary>
		public SqlParseItem SqlItemFirst
		{
			get
			{
				SqlParseItem first = this.SqlItemFirstKeyWord;
				if (first == null)
					first = this.SqlItemFirstContent;
				return first;
			}
		}
		/// <summary>
		/// Obsahuje docela poslední slovo této syntaxe, ať už je to poslední položka Content nebo KeyWord
		/// </summary>
		public SqlParseItem SqlItemLast
		{
			get
			{
				SqlParseItem last = this.SqlItemLastContent;
				if (last == null)
					last = this.SqlItemLastKeyWord;
				return last;
			}
		}
		/// <summary>
		/// Obsahuje docela první slovo KeyWord této syntaxe(nezohlední Content)
		/// </summary>
		public SqlParseItem SqlItemFirstKeyWord
		{ get { return _GetFirstItem(this.SqlItemKeywordList); } }
		/// <summary>
		/// Obsahuje docela poslední slovo KeyWord této syntaxe (nezohlední Content)
		/// </summary>
		public SqlParseItem SqlItemLastKeyWord
		{ get { return _GetLastItem(this.SqlItemKeywordList); } }
		/// <summary>
		/// Obsahuje docela první slovo Content této syntaxe(nezohlední KeyWord)
		/// </summary>
		public SqlParseItem SqlItemFirstContent
		{ get { return _GetFirstItem(this.SqlItemContentList); } }
		/// <summary>
		/// Obsahuje docela poslední slovo Content této syntaxe (nezohlední KeyWord)
		/// </summary>
		public SqlParseItem SqlItemLastContent
		{ get { return _GetLastItem(this.SqlItemContentList); } }
		/// <summary>
		/// Vrátí první prvek seznamu, nebo null když jě seznam prázdný.
		/// </summary>
		/// <param name="list"></param>
		/// <returns></returns>
		private SqlParseItem _GetFirstItem(List<SqlParseItem> list)
		{
			return (list.Count == 0 ? null : list[0]);
		}
		/// <summary>
		/// Vrátí poslední prvek seznamu, nebo null když jě seznam prázdný.
		/// </summary>
		/// <param name="list"></param>
		/// <returns></returns>
		private SqlParseItem _GetLastItem(List<SqlParseItem> list)
		{
			return (list.Count == 0 ? null : list[list.Count - 1]);
		}
		#endregion
		#region PODPORA PARSOVÁNÍ SYNTAXE
		/// <summary>
		/// Zjistí, zda dané vstupní SQL slovo může odpovídat některé z variant této syntaktické položky.
		/// Pokud toto slovo odpovídá začátku některé položky (například "LEFT" může odpovídat začátku varianty "LEFT OUTER JOIN"),
		/// pak si slovo LEFT zapamatuji a vrátím Partial.
		/// Pokud odpovídá přesně, vrátím Equal.
		/// Pokud nenajdu nic, vrátím NoMatch.
		/// Pokud na začátku mám v paměti nějaký začátek (když jsem minule vracel Partial), pak ten začátek použiju při hledání varianty.
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		internal SqlParseSyntaxMatchType GetSyntaxMatch(SqlParseItem sqlItem)
		{
			string prevText = this.PrevPartialText;
			this.PrevPartialText = null;
			this.CurrentMatchText = null;

			string sqlKeyWord = sqlItem.KeyWord;
			if (sqlKeyWord == null) return SqlParseSyntaxMatchType.NoMatch;

			string currentText = (String.IsNullOrEmpty(prevText) ? "" : prevText + " ") + sqlKeyWord;

			prevText = null;
			foreach (string kw in this.KeyWordList)
			{
				if (currentText == kw)
				{	// Plná shoda: vrátíme ji
					this.CurrentMatchText = kw;
					return SqlParseSyntaxMatchType.Equal;
				}
				if (kw.StartsWith(currentText))
				{	// Částečná shoda (začátek slova): zapamatujeme si ji, ale hledáme dál
					prevText = currentText;
				}
			}
			this.PrevPartialText = prevText;
			return (prevText == null ? SqlParseSyntaxMatchType.NoMatch : SqlParseSyntaxMatchType.Partial);
		}
		/// <summary>
		/// Text z předchozích Partial shod
		/// </summary>
		protected string PrevPartialText;
		/// <summary>
		/// Text aktuální varianty, která se shoduje s průběžně načítaným SQL textem.
		/// Je nastaveno na not null text pouze poté, kdy je z metody this.GetSyntaxMatch() vrácena hodnoda SqlParseSyntaxMatchType.Equal.
		/// </summary>
		public string CurrentMatchText { get; private set; }
		/// <summary>
		/// Ověří, zda vstupní SQL text je nějaké klíčové slovo z klíčových slov této syntaxe
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		internal bool IsAnyKeyword(SqlParseItem sqlItem)
		{
			if (sqlItem.TextComponent != SqlParseTextComponent.SqlText) return false;
			string keyWord = " " + sqlItem.KeyWord + " ";
			foreach (string kw in this.KeyWordList)
			{
				string kws = " " + kw + " ";
				if (kws.Contains(keyWord)) return true;
			}
			return false;
		}
		/// <summary>
		/// Přidá slova z daného textu do this.SqlItemKeywordList, pokud tam dosud neexistují.
		/// </summary>
		/// <param name="keyWords">Seznam položek klíčových slov</param>
		internal void AddKeywordItemWhenNotExists(IEnumerable<SqlParseItem> keyWords)
		{
			foreach (SqlParseItem sqlItem in keyWords)
				this.AddKeywordItemWhenNotExists(sqlItem);
		}
		/// <summary>
		/// Přidá slova z daného textu do this.SqlItemKeywordList, pokud tam dosud neexistují.
		/// </summary>
		/// <param name="keyWord">Slovo k přidání do seznamu klíčových slov</param>
		internal bool AddKeywordItemWhenNotExists(SqlParseItem keyWord)
		{
			if (!this.SqlItemKeywordList.Exists(itm => String.Equals(itm.Text, keyWord.Text, StringComparison.OrdinalIgnoreCase)))
			{
				keyWord.SetParentSyntaxItem(this);
				this.SqlItemKeywordList.Add(keyWord);
				return true;
			}
			return false;
		}
		/// <summary>
		/// Do svého seznamu SqlItemKeywordList přidá SQL položky ze seznamu sqlItems, přičemž do nich vloží jako syntax parenta sebe.
		/// Nové položky přidává na konec seznamu.
		/// </summary>
		/// <param name="sqlItem"></param>
		internal void AddKeywordItem(IEnumerable<SqlParseItem> sqlItems)
		{
			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
					AddKeywordItem(sqlItem);
			}
		}
		/// <summary>
		/// Do svého seznamu SqlItemKeywordList přidá SQL položku sqlItem, přičemž do ní vloží jako syntax parenta sebe.
		/// Nové položky přidává na konec seznamu.
		/// </summary>
		/// <param name="sqlItem"></param>
		internal void AddKeywordItem(SqlParseItem sqlItem)
		{
			sqlItem.SetParentSyntaxItem(this);
			this.SqlItemKeywordList.Add(sqlItem);
		}
		/// <summary>
		/// Do svého seznamu SqlItemContentList přidá SQL položky ze seznamu sqlItems, přičemž do nich vloží jako syntax parenta sebe.
		/// Nové položky přidává na začátek seznamu.
		/// </summary>
		/// <param name="sqlItems">Soupis položek k přidání</param>
		internal void AddContentItemOnBegin(IEnumerable<SqlParseItem> sqlItems)
		{
			foreach (SqlParseItem sqlItem in sqlItems)
				sqlItem.SetParentSyntaxItem(this);

			if (this.SqlItemContentList.Count > 0)
				this.SqlItemContentList.InsertRange(0, sqlItems);
			else
				this.SqlItemContentList.AddRange(sqlItems);
		}
		/// <summary>
		/// Do svého seznamu SqlItemContentList přidá SQL položku sqlItem, přičemž do ní vloží jako syntax parenta sebe.
		/// Položku přidá na začátek seznamu.
		/// </summary>
		/// <param name="sqlPrev"></param>
		internal void AddContentItemOnBegin(SqlParseItem sqlItem)
		{
			sqlItem.SetParentSyntaxItem(this);

			if (this.SqlItemContentList.Count > 0)
				this.SqlItemContentList.Insert(0, sqlItem);
			else
				this.SqlItemContentList.Add(sqlItem);
		}
		/// <summary>
		/// Do svého seznamu SqlItemContentList přidá SQL položky ze seznamu sqlItems, přičemž do nich vloží jako syntax parenta sebe.
		/// Nové položky přidává na konec seznamu.
		/// </summary>
		/// <param name="sqlItems">Soupis položek k přidání</param>
		internal void AddContentItemOnEnd(IEnumerable<SqlParseItem> sqlItems)
		{
			foreach (SqlParseItem sqlItem in sqlItems)
				sqlItem.SetParentSyntaxItem(this);

			this.SqlItemContentList.AddRange(sqlItems);
		}
		/// <summary>
		/// Do svého seznamu SqlItemContentList přidá SQL položku sqlItem, přičemž do ní vloží jako syntax parenta sebe.
		/// Položku přidá na konec seznamu.
		/// </summary>
		/// <param name="sqlPrev"></param>
		internal void AddContentItemOnEnd(SqlParseItem sqlItem)
		{
			sqlItem.SetParentSyntaxItem(this);
			this.SqlItemContentList.Add(sqlItem);
		}
		/// <summary>
		/// Sestaví znovu frontu (Queue) ze všech zdejších Items, pro následné další dekódování
		/// </summary>
		/// <returns></returns>
		internal Queue<SqlParseItem> GetAllItemsQueue()
		{
			Queue<SqlParseItem> sqlQueue = new Queue<SqlParseItem>();
			
			foreach (SqlParseItem sqlItem in this.SqlItemKeywordList)
				sqlQueue.Enqueue(sqlItem);
			foreach (SqlParseItem sqlItem in this.SqlItemContentList)
				sqlQueue.Enqueue(sqlItem);

			return sqlQueue;
		}
		#endregion
		#region MODIFIKACE SQL PŘÍKAZU (ADD / MODIFY / REMOVE SYNTAX)
		/// <summary>
		/// Přidá do určité syntaktické fráze nový výraz.
		/// Vrátí true, když se to podařilo.
		/// </summary>
		/// <param name="expression">Přidávaný výraz</param>
		/// <returns></returns>
		public bool AddSyntaxExpression(string expression)
		{
			return this._AddSyntaxExpression(expression, true, this.KeyWord);
		}
		/// <summary>
		/// Přidá do určité syntaktické fráze nový výraz.
		/// Vrátí true, když se to podařilo.
		/// </summary>
		/// <param name="expression">Přidávaný výraz</param>
		/// <param name="delimited">Oddělit od stávajícího obsahu (typicky čárka, závorky a AND, atd.)</param>
		/// <returns></returns>
		public bool AddSyntaxExpression(string expression, bool delimited)
		{
			return this._AddSyntaxExpression(expression, delimited, this.KeyWord);
		}
		/// <summary>
		/// Přidá do určité syntaktické fráze nový výraz.
		/// Vrátí true, když se to podařilo.
		/// </summary>
		/// <param name="expression">Přidávaný výraz</param>
		/// <param name="delimited">Oddělit od stávajícího obsahu (typicky čárka, závorky a AND, atd.)</param>
		/// <param name="keywords">Konkrétní klíčová slova (například, pokud je hlavní klíčové slovo JOIN, pak lze specifikovat LEFT OUTER JOIN)</param>
		/// <returns></returns>
		public bool AddSyntaxExpression(string expression, bool delimited, string keywords)
		{
			return this._AddSyntaxExpression(expression, delimited, keywords);
		}
		/// <summary>
		/// Přidá do této syntaxe nová klíčová slova. Pouze taková, která tam ještě nejsou.
		/// </summary>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <returns></returns>
		public bool AddKeyWord(string keywords)
		{
			return this._AddKeyWord(keywords, null);
		}
		/// <summary>
		/// Přidá do této syntaxe nová klíčová slova. Pouze taková, která tam ještě nejsou.
		/// </summary>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <param name="addAfterWords">Seznam klíčových slov, za které by se měl dodaný text přidávat (insert after)</param>
		/// <returns></returns>
		public bool AddKeyWord(string keywords, params string[] addAfterWords)
		{
			return this._AddKeyWord(keywords, addAfterWords);
		}
		/// <summary>
		/// Přidá do této syntaxe nová klíčová slova. Pouze taková, která tam ještě nejsou.
		/// </summary>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <param name="addAfterWords">Seznam klíčových slov, za které by se měl dodaný text přidávat (insert after)</param>
		/// <returns></returns>
		public bool AddKeyWord(string keywords, IEnumerable<string> addAfterWords)
		{
			return this._AddKeyWord(keywords, addAfterWords);
		}
		/// <summary>
		/// Přidá do určité syntaktické fráze nový výraz.
		/// Vrátí true, když se to podařilo.
		/// Výkonná metoda.
		/// </summary>
		/// <param name="expression">Přidávaný výraz</param>
		/// <param name="delimited">Oddělit od stávajícího obsahu (typicky čárka, závorky a AND, atd.)</param>
		/// <param name="keywords">Konkrétní klíčová slova (například, pokud je hlavní klíčové slovo JOIN, pak lze specifikovat LEFT OUTER JOIN)</param>
		/// <returns></returns>
		private bool _AddSyntaxExpression(string expression, bool delimited, string keywords)
		{
			// Vytvořím pole položek (Content) z výrazu:
			List<SqlParseItem> expItems = this.ParseText(expression, SqlParseCommandComponent.Parameter);
			// Pokud je dodaný výraz významově prázdný, skončíme:
			if (!SqlParseItem.IsListSignificant(expItems))
				return false;

			bool isAdded;
			// Pokud tento prvek syntaxe ještě nebyl použitý, musíme to zajistit teď :
			if (!this.IsUsed)
			{
				isAdded = this._ActivateThisKeyword(keywords);
				if (!isAdded) return false;
			}

			// Zpracovat předaný výraz a vložit jej do své syntaxe:
			isAdded = this._AddNewContent(expItems, delimited);

			return isAdded;
		}
		/// <summary>
		/// Přidá do této syntaxe nová klíčová slova. Pouze taková, která tam ještě nejsou.
		/// </summary>
		/// <param name="keywords">Klíčové slovo (slova) k přidání</param>
		/// <param name="addAfterWords">Seznam klíčových slov, za které by se měl dodaný text přidávat (insert after)</param>
		/// <returns></returns>
		private bool _AddKeyWord(string keywords, IEnumerable<string> addAfterWords)
		{
			// Vytvořím pole položek (KeyWords) z výrazu:
			List<SqlParseItem> keyItems = this.ParseText(keywords, SqlParseCommandComponent.KeyWord);
			// Pokud je dodaný výraz významově prázdný, skončíme:
			if (!SqlParseItem.IsListSignificant(keyItems))
				return false;
			
			bool isAdded;
			// Pokud tento prvek syntaxe ještě nebyl použitý, musíme to zajistit teď :
			if (!this.IsUsed)
			{
				isAdded = this._ActivateThisKeyword(keywords);
				if (!isAdded) return false;
			}

			// Najít slovo, za které se bude nový text přidávat:
			int indexLast = -1;
			if (addAfterWords != null)
				indexLast = this._FindFirstKeyWordIndex(addAfterWords);  // pokud volající sdělil, za která slova by rád nový text umístil, najdeme index prvního slova
			if (indexLast < 0)
				indexLast = this.SqlItemKeywordList.Count - 1;           // nenalezeno nebo nesdělil: najdeme poslední slovo
			if (indexLast < 0)
				return false;

			// Jednotlivá slova budeme vkládat do SqlTextu i do SqlItemKeywordList, pokud dosud v SqlItemKeywordList neexistují:
			foreach (SqlParseItem keyWord in keyItems)
			{	// Každé vstupní KeyWord
				if (!this.SqlItemKeywordList.Exists(itm => String.Equals(itm.Text, keyWord.Text, StringComparison.OrdinalIgnoreCase)))
				{	// Pokud dosud neexistuje
					keyWord.SetParentSyntaxItem(this);

					// Najdu KeyWord této syntaxe, za který budeme vkládat novou položku:
					SqlParseItem sqlLast = this.SqlItemKeywordList[indexLast];

					// Do SqlTextu insertujeme daný seznam, za daný prvek:
					this.ParentText.InsertSqlItemsAfter(sqlLast, new SqlParseItem[] { keyWord }, false);

					// Nové slovo vložím za prvek na daném indexu (tedy nikoli NA daný index, ale na index +1):
					if (indexLast >= (this.SqlItemKeywordList.Count - 1))
						this.SqlItemKeywordList.Add(keyWord);
					else
						this.SqlItemKeywordList.Insert((indexLast + 1), keyWord);
					indexLast++;
				}
			}

			return true;
		}
		/// <summary>
		/// Deaktivace tohoto prvku syntaxe = odebere všechna slova, která spadají do této syntaxe.
		/// </summary>
		public void Deactivate()
		{
			// Nejprve odeberu moje slova ze SqlCommandu, ten je odebere ze svého parenta = SqlText:
			this.ParentSqlCommand.RemoveItems(this.SqlItemKeywordList);
			this.ParentSqlCommand.RemoveItems(this.SqlItemContentList);

			// A nakonec je zahodím:
			this.SqlItemKeywordList.Clear();
			this.SqlItemContentList.Clear();
		}
		/// <summary>
		/// Najde a vrátí první slovo, které odpovídá některému z daných textů.
		/// Hledá v zadaném pořadí, jestliže tedy v reálném textu budou slova "AB CD EF GE", a zadáme přehled "XX", "EF", "AB", "GE",
		/// pak se nalezne shoda pro slovo "EF". 
		/// Protože XX neexistuje, a slova AB a GE hsou v seznamu hledaných slov později.
		/// </summary>
		/// <param name="keyWords"></param>
		/// <returns></returns>
		private int _FindFirstKeyWordIndex(IEnumerable<string> keyWords)
		{
			foreach (string keyWord in keyWords)
			{	// Hledám každé zadané slovo = zachovávám dané pořadí:
				for (int i = 0; i < this.SqlItemKeywordList.Count; i++)
				{	// Hledám v reálných Keywords = hledám první výskyt zleva:
					if (String.Equals(this.SqlItemKeywordList[i].Text, keyWord, StringComparison.OrdinalIgnoreCase))
						return i;
				}
			}
			return -1;
		}
		/// <summary>
		/// Zajistí přidání sebe sama jakožto syntaktické fráze do svého parent příkazu.
		/// Tedy = dojde k aktivaci této syntaktické fráze.
		/// </summary>
		/// <param name="keywords"></param>
		private bool _ActivateThisKeyword(string keywords)
		{
			// Pokud už jsem součástí příkazu, skončím a tvářím se jako že OK:
			if (this.IsUsed)
				return true;

			// Vytvořím pole položek (KeyWord) z výrazu:
			List<SqlParseItem> keySqlItems = this.ParseText(keywords, SqlParseCommandComponent.KeyWord);

			// Tuto sekvenci je třeba zařadit i do SqlTextu, hned za poslední položku předchozí syntaxe:
			//   (Předchozí syntaxe tu musí být vždy, ta je klíčem k celému příkazu, a je IsUsed):
			if (!this._InsertIntoTextAfterPrevSyntax(keySqlItems))
				return false;

			// Toto reálné klíčové slovo (slova) musím zařadit do svých klíčových slov:
			this.AddKeywordItem(keySqlItems);

			return true;
		}
		/// <summary>
		/// Do obsahu svého příkazu přidá další výraz(y).
		/// </summary>
		/// <param name="expItems">Soupis položek přidávaného výrazu</param>
		/// <param name="delimited"></param>
		/// <returns></returns>
		private bool _AddNewContent(List<SqlParseItem> expItems, bool delimited)
		{
			// Pokud je dodaný výraz významově prázdný, skončíme:
			if (!SqlParseItem.IsListSignificant(expItems))
				return false;

			// Obalí/oddělí současný obsah tak, aby k němu mohla být (na jeho konec) přidána další část:
			this._WrapCurrentContent(expItems);

			// Tuto sekvenci je třeba zařadit i do SqlTextu, hned za poslední položku této syntaxe:
			if (!this._InsertIntoTextAfterThisSyntax(expItems))
				return false;

			// Toto reálné slovo (slova) musím zařadit do svých slov:
			this.AddContentItemOnEnd(expItems);

			return true;
		}
		/// <summary>
		/// Obalí/oddělí současný obsah tak, aby k němu mohla být (na jeho konec) přidána další část.
		/// Záleží na typu příkazu a syntaxi, jinak se odděluje seznam fields (čárkou), jinak FROM ... JOIN (mezerou),
		/// jinak podmínky ve WHERE (závorky a AND), ...
		/// Oddělování řeší konkrétní potomek v override metodě WrapSyntaxContent().
		/// </summary>
		/// <param name="expItems">Soupis položek přidávaného výrazu</param>
		private void _WrapCurrentContent(List<SqlParseItem> expItems)
		{
			SqlParseCommand parentCommand = this.ParentSqlCommand;     // Objekt SQL příkazu, jehož je tato syntaxe součástí
			parentCommand.WrapSyntaxContent(this, expItems);
		}
		/// <summary>
		/// Vloží daný seznam položek (sekvenci SqlItem) za poslední položku této syntaxe.
		/// Vkládá položky jen do parent textu, nikoli do this syntaxe, od toho jsou jiné metody
		///   (typicky: this.AddContentItem(), this.AddKeywordItem()).
		/// </summary>
		/// <param name="sqlItems">Seznam položek</param>
		private bool _InsertIntoTextBeforeThisSyntax(List<SqlParseItem> sqlItems)
		{
			// Najdu první slovo obsahu syntaxe:
			SqlParseItem sqlFirst = this.SqlItemFirstContent;
			if (sqlFirst != null)
			{	// Content už existuje, vložím nový text před první jeho položku:
				this.ParentText.InsertSqlItemsBefore(sqlFirst, sqlItems, true);
			}
			else
			{	// Pokud syntaxe ještě nemá obsah, vložím dodané slovo za poslední slovo Keywordu:
				SqlParseItem sqlLast = this.SqlItemLastKeyWord;
				if (sqlLast == null)
					throw new IndexOutOfRangeException("Syntax Item is empty, cannot insert new content.");

				this.ParentText.InsertSqlItemsAfter(sqlLast, sqlItems, true);
			}
			return true;
		}
		/// <summary>
		/// Vloží daný seznam položek (sekvenci SqlItem) za poslední položku této syntaxe.
		/// Vkládá položky jen do parent textu, nikoli do this syntaxe, od toho jsou jiné metody
		///   (typicky: this.AddContentItem(), this.AddKeywordItem()).
		/// </summary>
		/// <param name="sqlItems"></param>
		private bool _InsertIntoTextAfterThisSyntax(List<SqlParseItem> sqlItems)
		{
			// Najdu poslední slovo této syntaxe:
			SqlParseItem sqlLast = this.SqlItemLast;
			if (sqlLast == null)
				return false;  // To je fakt divný, když jsme našli syntax prevSyntax která je IsUsed, proč jsme v ní nenašli SqlItem?

			// Do SqlTextu insertujeme daný seznam, za daný prvek:
			this.ParentText.InsertSqlItemsAfter(sqlLast, sqlItems, true);
			return true;
		}
		/// <summary>
		/// Vloží daný seznam položek (sekvenci SqlItem) za poslední položku předchozí použité syntaxe.
		/// Vkládá položky jen do parent textu, nikoli do this syntaxe, od toho jsou jiné metody
		///   (typicky: this.AddContentItem(), this.AddKeywordItem()).
		/// Pozor: zde jsem v některé syntaxi, hledám ve svém parentu předchozí syntaxi.
		/// Samotnou položku syntaxe nikam vkládat nemusím, ty jsou přítomny vždy.
		/// </summary>
		/// <param name="sqlItems"></param>
		private bool _InsertIntoTextAfterPrevSyntax(List<SqlParseItem> sqlItems)
		{
			SqlParseCommand parentCommand = this.ParentSqlCommand;     // Objekt SQL příkazu, jehož je tato syntaxe součástí

			// Nejdřív najdu předchozí syntaxi:
			SqlParseSyntaxItem prevSyntax = null;
			bool find = false;
			List<SqlParseSyntaxItem> syntaxList = new List<SqlParseSyntaxItem>(parentCommand.SyntaxItemsAll);
			for (int i = syntaxList.Count - 1; i >= 0; i--)
			{	// Procházím od konce: nejdřív najdu sebe, a při tom nastavím příznak find = true, a pak převezmu nejbližší použitý prvek:
				if (!find && Object.ReferenceEquals(syntaxList[i], this))
				{	// Hledám sebe, a našel jsem se:
					find = true;
				}
				else if (find && syntaxList[i].IsUsed)
				{
					prevSyntax = syntaxList[i];
					break;
				}
			}
			// Nenašli? Sice je to divné, ale nechť: 
			if (prevSyntax == null)
				return false;

			// Pak převezmu poslední slovo této syntaxe:
			SqlParseItem sqlLast = prevSyntax.SqlItemLast;
			if (sqlLast == null)
				return false;  // To je fakt divný, když jsme našli syntax prevSyntax která je IsUsed, proč jsme v ní nenašli SqlItem?

			// Do SqlTextu insertujeme daný seznam, za daný prvek:
			this.ParentText.InsertSqlItemsAfter(sqlLast, sqlItems, true);
			return true;
		}
		#endregion
		#region PODPORA PRO MODIFIKACI OBSAHU
		/// <summary>
		/// Rozdělí zadaný výraz na jednotlivé položky, vloží do nich jako parenta sebe a svůj parent text, a vloží do nich druh cmdComponent.
		/// Pokud je parsován Keyword, pak jako cmdComponent se předává SqlParseCommandComponent.KeyWord, pokud je parsován Content, pak cokoliv.
		/// </summary>
		/// <param name="expression">Text k parsování</param>
		/// <param name="cmdComponent">Druh textu: Pokud je parsován Keyword, pak jako cmdComponent se předává SqlParseCommandComponent.KeyWord, pokud je parsován Content, pak cokoliv.</param>
		/// <returns></returns>
		public List<SqlParseItem> ParseText(string expression, SqlParseCommandComponent cmdComponent)
		{
			return SqlParseCommand.ParseText(expression, this.ParentText, cmdComponent);
		}
		/// <summary>
		/// Pokud v tomto prvku je neprázdný Content, obalí ho závorkami "(" a ")".
		/// </summary>
		public void ParenthesiseContent(bool force)
		{
			if (!this.IsContentSignificant) return;          // Nic není, nebudu nic obalovat.

			// Ozávorkovat povinně / nepovinně:
			bool parenth = (force || !SqlParseCommand.IsParenthesiseCompleted(this.SqlItemContentList));
			if (!parenth) return;                            // Není to povinné, a není to zapotřebí = nebudu závorkovat a skončím.

			// Ozávorkovat:
			this.AddContentOnBegin("(");
			this.AddContentOnEnd(")");

			// Přepočítat úroveň závorek pro celý Content:
			SqlParseCommand.SetBracketLevel(this.SqlItemContentList);
		}
		/// <summary>
		/// Přidá daný text na začátek obsahu (Content) této syntaktické fráze
		/// </summary>
		/// <param name="text"></param>
		public void AddContentOnBegin(string text)
		{
			// Parsovat text, vložit do něj cmd typ pro Content (ne Keyword):
			List<SqlParseItem> sqlItems = this.ParseText(text, SqlParseCommandComponent.Parameter);

			// Tuto sekvenci je třeba zařadit i do SqlTextu, hned za poslední položku této syntaxe:
			if (this._InsertIntoTextBeforeThisSyntax(sqlItems))
				// Toto reálné klíčové slovo (slova) musím zařadit do svých klíčových slov (na konec):
				this.AddContentItemOnBegin(sqlItems);
		}
		/// <summary>
		/// Přidá daný text na konec obsahu (Content) této syntaktické fráze.
		/// Přímo za poslední prvek když existuje, anebo přímo do seznamu pokud je prázdný.
		/// Současně se text přidává do podkladové vrstvy SqlText.
		/// Metoda nepřidává žádné další prvky nad rámec daného textu.
		/// </summary>
		/// <param name="text"></param>
		public void AddContentOnEnd(string text)
		{
			// Parsovat text, vložit do něj cmd typ pro Content (ne Keyword):
			List<SqlParseItem> sqlItems = this.ParseText(text, SqlParseCommandComponent.Parameter);

			// Tuto sekvenci je třeba zařadit i do SqlTextu, hned za poslední položku této syntaxe:
			if (this._InsertIntoTextAfterThisSyntax(sqlItems))
				// Toto reálné klíčové slovo (slova) musím zařadit do svých klíčových slov (na konec):
				this.AddContentItemOnEnd(sqlItems);
		}
		/// <summary>
		/// Oddělí stávající obsah syntaktické fráze čárkou (kterou přidá na konec).
		/// Přímo za poslední prvek když existuje, anebo přímo do seznamu pokud je prázdný.
		/// Současně se text přidává do podkladové vrstvy SqlText.
		/// Metoda nepřidává žádné další prvky nad rámec daného textu.
		/// </summary>
		public void WrapWithComma()
		{
			if (!this.IsContentSignificant) return;          // Nic není, nebudu nic obalovat.
			this.AddContentOnEnd(",");
		}
		/// <summary>
		/// Oddělí stávající obsah syntaktické fráze jednou mezerou (kterou přidá na konec).
		/// </summary>
		public void WrapWithSpace()
		{
			if (!this.IsContentSignificant) return;          // Nic není, nebudu nic obalovat.
			this.AddContentOnEnd(" ");
		}
		/// <summary>
		/// Oddělí stávající obsah syntaktické fráze tak, že ji obalí závorkami, a na konec přidá daný text (typicky "AND").
		/// </summary>
		/// <param name="addText">Text, který se přidá za závorku. 
		/// Mezery před a za slovem jsou doporučené, ale nejsou povinné. 
		/// Metoda je sama nepřidává.</param>
		public void WrapWithParenthesize(string addText)
		{
			if (!this.IsContentSignificant) return;          // Nic není, nebudu nic obalovat.
			this.ParenthesiseContent(false);
			if (!String.IsNullOrEmpty(addText))
				this.AddContentOnEnd(addText);
		}
		#endregion
		#region PODPORA MODIFIKACÍ SYNTAXE - PŘESUN ČÁSTI CONTENT DO KEYWORD
		/// <summary>
		/// V rámci této (this) syntaxe přesune z jejího začátku slova odpovídající daným modifikacím do KeyWordListu.
		/// Typicky se použije například na frázi "ORDER" pro přesun pomocného slova "BY" z Contentu do keywordu.
		/// Parametry jsou soupis doplňkových slov, která mohou být na začátku Contentu.
		/// Slov může být libovolné množství.
		/// Každý jeden string vyjadřuje jednu kombinaci textu (i víceslovnou, pokud má být v explicitním formátu).
		/// Příklady: "BY", "WITH TIES", "DISTINCT", "TOP * PERCENT", "TOP * %" (kde * zastupuje libovolný výraz).
		/// Slova se v těchto položkách oddělují jednou mezerou, jinak nejsou rozdělena!!!
		/// </summary>
		/// <param name="modificators">Seznam doplňkových slov k přesunutí z Content do Keyword.</param>
		internal void SyntaxModify(params string[] modificators)
		{
			// Připravím seznam modifikátorů:
			List<ModItem> modList = new List<ModItem>();
			foreach (string modTxt in modificators)
				modList.Add(new ModItem(modTxt));
			if (modList.Count == 0) return;

			// Podívám se na Content:
			while (modList.Count > 0 && this.SqlItemContentList.Count > 0)
			{
				ModItem modMatch = null;           // Modifikátor, který nejlépe vyhovuje (Full shoda, nejdelší ze známých modů)
				List<SqlParseItem> sqlList = new List<SqlParseItem>();  // Průběžně doplňovaný seznam slov ("WITH". "WITH","TIES". "WITH","TIES","slovo".)
				for (int i = 0; i < this.SqlItemContentList.Count; i++)
				{
					SqlParseItem sqlItem = this.SqlItemContentList[i];
					if (sqlItem.IsSqlVoid)
						continue;

					// Nyní mám v currText prvních několik významných slov. Zeptáme se všech modů, zda jim to odpovídá:
					bool nextItem = false;
					sqlList.Add(sqlItem);
					foreach (ModItem modItem in modList)
					{
						// Odpovídá tento mod aktuálnímu textu?
						ModEquation modEqut = modItem.CompareWithSqlItems(sqlList, i);
						if (modEqut == ModEquation.Full && (modMatch == null || modMatch.Count < modItem.Count))
							// Pokud tento mod odpovídá přesně, a (dosud nemáme žádný jiný mod, nebo máme kratší), zapamatujeme si jej:
							modMatch = modItem;
						else if (modEqut == ModEquation.Begin)
							// Pokud tento mod má začátek shodný s aktuálním textem, ale mod chce víc slov, podíváme se dál:
							nextItem = true;
					}
					if (nextItem)
						continue;
					break;
				}

				// Pokud jsme nenašli žádný mod, skončíme:
				if (modMatch == null)
					break;

				// Pokud jsme našli některý mod, tak jej přeneseme z Content do Keyword, odebereme z Modů, a zkusíme to znovu:
				for (int i = 0; i <= modMatch.FullMatchOnIndex; i++)
				{
					if (this.SqlItemContentList.Count == 0) break;              // Pojistka
					SqlParseItem sqlItem = this.SqlItemContentList[0];          // První položku z Content...
					sqlItem.SetTextComponent(SqlParseTextComponent.SqlText);    // Zajistím, že to bude SqlText typu KeyWord
					sqlItem.SetCmdComponent(SqlParseCommandComponent.KeyWord);
					this.SqlItemKeywordList.Add(sqlItem);                       // Přidat položku na konec KeyWords
					this.SqlItemContentList.RemoveAt(0);                        // První položku z Content smazat
				}
				int modIndx = modList.FindIndex(mod => Object.ReferenceEquals(mod, modMatch));
				if (modIndx < 0) break;
				modList.RemoveAt(modIndx);
			}
		}
		/// <summary>
		/// Třída pro uložení modifikátoru klíčového slova v rámci metody SyntaxModify()
		/// </summary>
		protected class ModItem
		{
			public ModItem(string item)
			{
				this.FullMatchOnIndex = -1;
				if (String.IsNullOrEmpty(item))
					this.Items = new string[0];
				else
					this.Items = item.Trim().ToUpper().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);   // Položky = slova oddělená mezerami
			}
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				string result = "";
				foreach (string item in this.Items)
					result += (result.Length == 0 ? "" : " ") + item;
				return result;
			}
			/// <summary>
			/// Položky = slova oddělená mezerami
			/// </summary>
			internal string[] Items;
			/// <summary>
			/// Počet slov v této položce
			/// </summary>
			internal int Count { get { return this.Items.Length; } }
			/// <summary>
			/// Index parent seznamu, na kterém se nachází poslední slovo této položky.
			/// </summary>
			internal int FullMatchOnIndex { get; private set; }
			/// <summary>
			/// Zjistí, zda aktuální SQL text odpovídá zdejšímu modifikátoru, a případně jak dobře.
			/// Pokud je například tento modifikátor "WITH TIES", a na vstupu je "WITH", pak se vrátí hodnota 
			/// </summary>
			/// <param name="sqlList"></param>
			/// <returns></returns>
			internal ModEquation CompareWithSqlItems(List<SqlParseItem> sqlList, int currentIndex)
			{
				int inpLen = sqlList.Count;
				int modLen = this.Items.Length;
				int minLen = (inpLen < modLen ? inpLen : modLen);       // Min(inpLen, modLen)
				if (minLen == 0) return ModEquation.None;
				for (int i = 0; i < minLen; i++)
				{
					string inpTxt = sqlList[i].Text.ToUpper();
					string modTxt = this.Items[i];
					if (modTxt != "*" && inpTxt != modTxt)
						// Pokud v definici je něco jiného než * (zástupný znak, jemuž vyhovuje všechno), 
						//  a pokud se vstupní text liší od požadovaného, vrátím None:
						return ModEquation.None;

				}
				// Všechny známé položky jsou shodné:
				if (inpLen < modLen)
					// Na vstupu je toho méně, než potřebujeme porovnat, ale začátek je shodný:
					return ModEquation.Begin;
				// Na vstupu je toho dostatek na porovnání, a to co potřebuji je shodné:
				if (inpLen == modLen)
					this.FullMatchOnIndex = currentIndex;
				return ModEquation.Full;
			}
		}
		/// <summary>
		/// Shoda vstupního textu s porovnávaným textem
		/// </summary>
		protected enum ModEquation { None, Begin, Full }
		#endregion
	}
	#endregion
	#region ENUM SqlParseSyntaxMatchType
	/// <summary>
	/// Stav porovnání nalezeného slova se syntaktickou položkou
	/// </summary>
	internal enum SqlParseSyntaxMatchType
	{
		/// <summary>
		/// Dané slovo není SqlText (jde o literál, string, operátor)
		/// </summary>
		NoSqlText = 1,
		/// <summary>
		/// Dané slovo neodpovídá žádnému slovu této syntaxe
		/// </summary>
		NoMatch,
		/// <summary>
		/// Začátek odpovídá (například jsme v SQL textu nalezli slova "LEFT OUTER", a syntaxe má znít "LEFT OUTER JOIN",
		/// takže očekáváme SQL položku s textem "JOIN".
		/// </summary>
		Partial,
		/// <summary>
		/// Přesná shoda, například jsme našli exaktně slovo "WHERE", 
		/// anebo jsme se právě teď dočkali slova "JOIN" když před tím jsme našli 
		/// a částečně odsouhasili (SqlParseSyntaxMatchType.Partial) slova "LEFT OUTER".
		/// </summary>
		Equal
	}
	#endregion
}
